﻿/*******************************************************************************************************************************************
Modification History:
********************************************************************************************************************************************
Date		Author		    Description
********************************************************************************************************************************************
05/08/2011	Brian Kuhn	    Created Subscription class
*******************************************************************************************************************************************/
using System;

namespace Concise
{
    /// <summary>
    /// Represents a local subscription maintained by the service bus that supports invocation of registered callbacks and their associated criteria.
    /// </summary>
    public class Subscription : IEquatable<Subscription>
    {
        //=======================================================================================================
        //  Constructors
        //=======================================================================================================
        #region Subscription()
        /// <summary>
        /// Initializes a new instance of the <see cref="Subscription"/> class.
        /// </summary>
        public Subscription()
        {

        }
        #endregion

        #region Subscription(WeakAction callback)
        /// <summary>
        /// Initializes a new instance of the <see cref="Subscription"/> class 
        /// using the specified <paramref name="callback"/> method.
        /// </summary>
        /// <param name="callback">The <see langword="delegate"/> method to execute when a message is received.</param>
        public Subscription(WeakAction callback) : this()
        {
            this.Callback   = callback;
        }
        #endregion

        #region Subscription(WeakAction callback, ISpecification criteria)
        /// <summary>
        /// Initializes a new instance of the <see cref="Subscription"/> class 
        /// using the specified <paramref name="callback"/> method and <paramref name="criteria"/>.
        /// </summary>
        /// <param name="callback">The <see langword="delegate"/> method to execute when a message is received.</param>
        /// <param name="criteria">The set of criteria a message must satisfy in order for the <paramref name="callback"/> delegate to be invoked.</param>
        public Subscription(WeakAction callback, ISpecification criteria) : this(callback)
        {
            this.Criteria   = criteria;
        }
        #endregion

        #region Subscription(WeakAction callback, ISpecification criteria, Type type)
        /// <summary>
        /// Initializes a new instance of the <see cref="Subscription"/> class 
        /// using the specified <paramref name="callback"/> method, <paramref name="criteria"/> and message <paramref name="type"/>.
        /// </summary>
        /// <param name="callback">The <see langword="delegate"/> method to execute when a message is received.</param>
        /// <param name="criteria">The set of criteria a message must satisfy in order for the <paramref name="callback"/> delegate to be invoked.</param>
        /// <param name="type">The message type for the subscription.</param>
        public Subscription(WeakAction callback, ISpecification criteria, Type type) : this(callback, criteria)
        {
            this.MessageType    = type;
        }
        #endregion

        //=======================================================================================================
        //  Public Properties
        //=======================================================================================================
        #region Callback
        /// <summary>
        /// Gets or sets the <see langword="delegate"/> method to execute when a message is received.
        /// </summary>
        /// <value>
        /// A <see cref="WeakAction"/> that represents the <see langword="delegate"/> method to execute when a message is received. 
        /// The default value is a <see langword="null"/> reference (Nothing in Visual Basic).
        /// </value>
        public WeakAction Callback
        {
            get
            {
                return _subscriptionCallback;
            }

            set
            {
                if (!WeakAction.Equals(_subscriptionCallback, value))
                {
                    _subscriptionCallback = value;
                }
            }
        }
        private WeakAction _subscriptionCallback;
        #endregion

        #region Criteria
        /// <summary>
        /// Gets or sets the set of criteria a message must satisfy in order for the callback delegate to be invoked.
        /// </summary>
        /// <value>
        /// A <see cref="ISpecification"/> that describes the set of criteria a message must satisfy in order for the <see cref="Callback"/> to be invoked. 
        /// The default value is a <see langword="null"/> reference (Nothing in Visual Basic).
        /// </value>
        public ISpecification Criteria
        {
            get
            {
                return _subscriptionCriteria;
            }

            set
            {
                _subscriptionCriteria = value;
            }
        }
        private ISpecification _subscriptionCriteria;
        #endregion

        #region MessageType
        /// <summary>
        /// Gets or sets the message type for this subscription.
        /// </summary>
        /// <value>
        /// The message <see cref="Type"/> for this subscription. 
        /// The default value is a <see langword="null"/> reference (Nothing in Visual Basic).
        /// </value>
        public Type MessageType
        {
            get
            {
                return _subscriptionMessageType;
            }

            set
            {
                if (!Type.Equals(_subscriptionMessageType, value))
                {
                    _subscriptionMessageType = value;
                }
            }
        }
        private Type _subscriptionMessageType;
        #endregion

        //=======================================================================================================
        //  IEquatable<Subscription> Implementation
        //=======================================================================================================
        #region Equals(Subscription other)
        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns><see langword="true"/> if the current object is equal to the other parameter; otherwise, <see langword="false"/>.</returns>
        public bool Equals(Subscription other)
        {
            if (other == null)
            {
                return false;
            }

            if (!WeakAction.Equals(this.Callback, other.Callback))
            {
                return false;
            }
            else if (!Type.Equals(this.MessageType, other.MessageType))
            {
                return false;
            }

            else if (this.Criteria == null && other.Criteria != null)
            {
                return false;
            }
            else if (this.Criteria != null)
            {
                return this.Criteria.Equals(other.Criteria);
            }

            return true;
        }
        #endregion

        #region Equals(object obj)
        /// <summary>
        /// Determines whether the specified <see cref="Object"/> is equal to the current <see cref="Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="Object"/> to compare with the current <see cref="Object"/>.</param>
        /// <returns>
        /// <see langword="true"/> if the specified <see cref="Object"/> is equal to the current <see cref="Object"/>; otherwise, <see langword="false"/>.
        /// </returns>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as Subscription);
        }
        #endregion

        #region Equals(Subscription first, Subscription second)
        /// <summary>
        /// Determines whether two specified <see cref="Subscription"/> objects have the same value.
        /// </summary>
        /// <param name="first">The first object to compare, or <see langword="null"/>.</param>
        /// <param name="second">The second object to compare, or <see langword="null"/>.</param>
        /// <returns>
        /// <see langword="true"/> if the value of <paramref name="first"/> object is the same as the value of the <paramref name="second"/> object; otherwise, <see langword="false"/>.
        /// </returns>
        public static bool Equals(Subscription first, Subscription second)
        {
            if (first == null && second != null)
            {
                return false;
            }

            if(first != null && second == null)
            {
                return false;
            }

            if (first == null && second == null)
            {
                return true;
            }

            return first.Equals(second);
        }
        #endregion

        #region GetHashCode()
        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        /// <a href="http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx"/>
        public override int GetHashCode()
        {
            int callbackHashCode    = this.Callback != null ? this.Callback.GetHashCode() : 0;
            int criteriaHashCode    = this.Criteria != null ? this.Criteria.GetHashCode() : 0;

            /*
             * The 23 and 37 are arbitrary numbers which are co-prime.
             * 
             * The benefit of the below over the XOR (^) method is that if you have a type 
             * which has two values which are frequently the same, XORing those values 
             * will always give the same result (0) whereas the above will 
             * differentiate between them unless you're very unlucky.
            */
            int hashCode    = 23;
            hashCode        = hashCode * 37 + callbackHashCode;
            hashCode        = hashCode * 37 + criteriaHashCode;
            
            return hashCode;
        }
        #endregion
    }
}
